#ifndef NETWORKTESTER_H
#define NETWORKTESTER_H

#include "networkfactory.h"
#include "network.h"

#include <QTime>
#include "GraphWidget/GraphWidget.h"

class NetworkTester
{
public:
    static bool testAll()
    {
        int observations[] = {2, 5};
        int queries[]      = {0, 3};

        /*multiQueryObserved("multi_param_net", 2, observations, 2, queries);
        testPruneChain("forward_flow", 4, 0, 3);
        testPruneChain("forward_flow_2", 4, 1, 2);
        testPruneChain("backward_flow", 4, 3, 0);
        testPruneY("v_net", 0, 2, 0);
        testPruneY("y_net", 2, 4, 0);
        testPruneY("y_net_long", 2, 2, 0);
        testPrunePeak("peak_net", 0, 1);
        testPrunePeak("peak_left_net", 1, 2);
        testFunkySimple();*/

        //testRandomNetwork("Random", 80, 100, 100, 0);
        computeBigOh(100,10000, 500, 10, 15, 15);
    }

    static void computeBigOh(int start, int maxRange, int step, int maxReps, int numObserved, int numQueries)
    {

        QVector<float> runTimes;
        QVector<float> nodeCounts;
        QVector<QPointF> timeData;

        int numNodes;
        qint64 totalNodes;
        float totalTime;

        for(int i = start; i < maxRange; i+= step)
        {
            totalTime  = 0.0;
            totalNodes = 0.0;
            qDebug() << QString::number(i);

            for(int j = 0; j < maxReps; j++)
            {
                totalTime  += testPruneChain(i, 0, i-1);
                totalNodes += i; //numNodes;
                //qDebug() << "\t" << QString::number(j) << totalTime << totalNodes;
            }



            float avgTime  = totalTime  / (float)maxReps;
            float avgNodes = totalNodes / (float)maxReps;

            runTimes.append(avgTime);
            nodeCounts.append(avgNodes);

            qDebug() << "\t  " << avgTime << avgNodes;


            timeData.append(QPointF(nodeCounts.last(), runTimes.last()));
        }

        graphComplexity(runTimes, nodeCounts);

        GraphWidget * widget = new GraphWidget();
        widget->graphLine(timeData, Qt::red, 1.0f, "Time vs. Number of Nodes");
        widget->autoResizeWindowRect();
        widget->show();
    }

    static void graphComplexity(QVector<float> runTimes, QVector<float> nodeCounts)
    {
        QVector<float> ns;
        QVector<float> nLogN;
        QVector<float> nSqrd;
        QVector<float> nSqrdLogN;

        double n, t;

        for(int i = 0; i < runTimes.count(); i++)
        {
            n = nodeCounts.at(i);
            t = runTimes.at(i);

            ns.append(n / t);
            nLogN.append(n * log(n) / t);
            nSqrd.append(pow(n, 2) / t);
            nSqrdLogN.append(n * n * log(n) / t);
        }

        GraphWidget * widget = new GraphWidget();
        widget->setTitle("Complexity");
        //widget->graphLine(ns, 0, 1, Qt::green, 1.0f, "n");
        //widget->graphLine(nLogN, 0, 1, Qt::red, 1.0f, "n log n");
        widget->graphLine(nSqrd, 0, 1, Qt::yellow, 1.0f, "n^2");
        widget->graphLine(nSqrdLogN, 0, 1, Qt::green, 1.0f, "n^2 log n");
        widget->autoResizeWindowRect();
        widget->show();
    }

    static float testRandomNetwork(int numLevels, int numObserved, int numQueries, int numCrossLinks, int * nodeCount)
    {
        Network * network = NetworkFactory::buildRandomNetwork(numLevels, numCrossLinks);

        int numNodes = network->getNodes()->count();
        QVector<Node *> queryNodes;
        for(int i = 0; i < numQueries; i++)
        {
            int index = rand() % numNodes;
            Node * node = network->getNodes()->at(index);
            if(!queryNodes.contains(node))
            {
                queryNodes.append(node);
            }
        }

        QVector<Node *> observedNodes;
        for(int i = 0; i < numObserved; i++)
        {
            int index = rand() % numNodes;
            Node * node = network->getNodes()->at(index);
            if(!(queryNodes.contains(node) || observedNodes.contains(node)));
            {
                observedNodes.append(node);
            }
        }

        //qDebug() << "-----" << network->getNodes()->count();

        (*nodeCount) = numNodes;

        return testPrune(network, &queryNodes, &observedNodes);
    }

    static bool multiQueryObserved(QString label, int numObservations, int * oNodes, int numQueries, int * qNodes)
    {
        Network * vNet     = NetworkFactory::buildYNetwork(0);
        Network * chainNet = NetworkFactory::buildChainNetwork(2);

        Network * network = new Network();
        network->addSubnet(vNet);
        network->addSubnet(chainNet);

        QVector<Node *> * nodes = network->getNodes();
        Node * n2 = nodes->at(2);
        Node * n4 = nodes->at(4);

        n4->addParentNode(n2);

        QVector<Node *> queryNodes;
        for(int i = 0; i < numQueries; i++)
        {
            queryNodes.push_back(nodes->at(qNodes[i]));
        }

        QVector<Node *> observedNodes;
        for(int i = 0; i < numObservations; i++)
        {
            observedNodes.push_back(nodes->at(oNodes[i]));
        }

        testPrune(network, &queryNodes, &observedNodes);
    }

    static float testPruneChain(int count, int observationNode, int queryNode)
    {
        Network * network = NetworkFactory::buildChainNetwork(count - 1);

        QVector<Node *> queryNodes;
            queryNodes.push_back(network->getNode(queryNode));

        QVector<Node *> observedNodes;
            observedNodes.push_back(network->getNode(observationNode));

        return testPrune(network, &queryNodes, &observedNodes);
    }



    static bool testPruneY(QString label, int count, int observationNode, int queryNode)
    {

        Network * network = NetworkFactory::buildYNetwork(count);

        QVector<Node *> queryNodes;
            queryNodes.push_back(network->getNode(queryNode));

        QVector<Node *> observedNodes;
            observedNodes.push_back(network->getNode(observationNode));

        testPrune(network, &queryNodes, &observedNodes);
    }

    static bool testPrunePeak(QString label, int observationNode, int queryNode)
    {
        Network * network = NetworkFactory::buildPeakNetwork();

        QVector<Node *> queryNodes;
            queryNodes.push_back(network->getNode(queryNode));

        QVector<Node *> observedNodes;
            observedNodes.push_back(network->getNode(observationNode));

        testPrune(network, &queryNodes, &observedNodes);
    }


    static bool testFunkySimple()
    {
        Network * network = NetworkFactory::buildFunkySimple();

        QVector<Node *> queryNodes;
            queryNodes.push_back(network->getNode(0));

        QVector<Node *> observedNodes;
            observedNodes.push_back(network->getNode(2));

        testPrune(network, &queryNodes, &observedNodes);
    }


    static float testPrune(Network * network, QVector<Node *> * queryNodes, QVector<Node *> * observedNodes)
    {
        for(int i = 0; i < observedNodes->count(); i++)
        {
            observedNodes->at(i)->setObserved(true);
        }

       // network->toDotFile("/home/sam/Desktop/dot_shiz/original_" + label + ".out", queryNodes);


        QTime time = QTime::currentTime();

        QVector<Node *> * necessary = network->prune(queryNodes, observedNodes);

        float elapsed = (float)(time.elapsed()) / 1000.0;

        //network->toDotFile("/home/sam/Desktop/dot_shiz/a.out", queryNodes, necessary);

        delete necessary;
        delete network;

        return elapsed;




    }


};

#endif // NETWORKTESTER_H
